The import statements and setup functions required to run this program are located below.
import warnings
warnings.filterwarnings("ignore")
import ipywidgets as widgets
from ipywidgets import interact, interact_manual
from IPython.display import Javascript, display
import ipython_blocking
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
import pandas_datareader.data as web
import talib
from talib.abstract import *
import matplotlib.pyplot as plt
%matplotlib inline
import plotly.offline as py
py.init_notebook_mode(connected = False)
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
Below, the user should input a stock ticker symbol (the code used to identify a stock or cryptocurrency). Some good examples are:
The code will resume once the Start button is clicked.
ticker = widgets.Text(description = 'Stock Ticker')
button = widgets.Button(description = 'Start')
box = widgets.VBox(children = [ticker, button])
box
%blockrun button
The code below uses pandas-datareader to get information about the selected stock from Yahoo Finance.
stock = web.DataReader(ticker.value, 'yahoo').reset_index()
stock = round(stock, 2)
stock['Volume'] = stock['Volume'].round(0).astype(int)
stock
The code below calculates the simple moving average for the stock price over the past 200 days (SMA200) and past 50 days (SMA50).
stock['SMA200'] = talib.SMA(stock['Close'], timeperiod = 200)
stock['SMA50'] = talib.SMA(stock['Close'], timeperiod = 50)
stock['EMA26'] = talib.EMA(stock['Close'], timeperiod = 26)
stock['EMA12'] = talib.EMA(stock['Close'], timeperiod = 12)
stock['EMA9'] = talib.EMA(stock['Close'], timeperiod = 9)
stock['MACD'], stock['MACDSignal'], stock['MACDHist'] = talib.MACD(stock['Close'], fastperiod = 12, slowperiod = 26, signalperiod = 9)
stock['MACDHist_SMA'] = talib.SMA(stock['MACDHist'], timeperiod = 10)
The code below calculates the relative strength index using a 14 day period. Relative sterength is calculated by doing: $RS=\frac{avg. gain}{avg. loss}$. RSI then uses the following formula: $RSI=100-\frac{100}{1+RS}$.
stock['RSI'] = talib.RSI(stock['Close'], timeperiod = 14)
stock['RSI_SMA'] = talib.SMA(stock['RSI'], timeperiod = 10)
The on-balance volume indicator measures cumulative buying/selling pressure by adding the volume on up days and subtracting volume on down days. It can be considered a running total of the stock's trading volume.
stock['OBV'] = talib.OBV(stock['Close'], stock['Volume']).round(0).astype(int)
stock['OBV_SMA'] = talib.SMA(stock['OBV'], timeperiod = 10)
The code below displays the retrieved stock data and all of the calculations performed.
stock = round(stock, 2)
stock
fig = go.Figure(data = [go.Candlestick(x = stock['Date'],
open = stock['Open'],
high = stock['High'],
low = stock['Low'],
close = stock['Close'])])
fig.update_layout(title_text = 'Candlestick',
xaxis_title = 'Date',
yaxis_title = 'Price',
xaxis_rangeslider_visible = True)
fig.update_yaxes(nticks = 10)
fig.show()
The interactive line graph below shows the closing price, the 200-day simple moving average, and the 50-day simple moving average for the stock. Moving averages show what is hapenning to the price of a stock (on average) over time:
fig = go.Figure()
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Close'], name = 'Closing Price'))
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['SMA200'], name = '200-Day Simple Moving Average'))
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['SMA50'], name = '50-Day Simple Moving Average'))
fig.update_layout(title_text = 'Simple Moving Average (SMA) Crossovers',
xaxis_title = 'Date',
yaxis_title = 'Price',
xaxis_rangeslider_visible = True)
fig.update_yaxes(nticks = 10)
fig.show()
The interactive graph below shows the moving average convergence divergence (MACD), signal line (MACDSignal), and histogram (MACDHist).
A buy signal occurs when:
A sell signal occurs when:
The histogram serves as a warning, not a signal:
fig = make_subplots(rows = 2, cols = 1, shared_xaxes = True)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Close'], name = 'Closing Price'), row = 1, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([0]).repeat(len(stock)), showlegend = False, hoverinfo = 'skip', line = dict(color = 'black', dash = 'dot')), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['MACD'], name = 'Moving Average Convergence Divergence'), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['MACDSignal'], name = 'MACD Signal Line'), row = 2, col = 1)
fig.add_trace(go.Bar(x = stock['Date'], y = stock['MACDHist'], name = 'MACD Histogram', marker_color = 'black'), row = 2, col = 1)
fig.update_layout(title_text = 'Moving Average Convergence Divergence (MACD)', height = 800)
fig.update_xaxes(title_text = 'Date', row = 2, col = 1)
fig.update_yaxes(title_text = 'Price', row = 1, col = 1)
fig.update_yaxes(title_text = 'MACD', row = 2, col = 1)
fig.update_yaxes(nticks = 10, row = 1, col = 1)
fig.update_yaxes(nticks = 10, row = 2, col = 1)
fig.update_yaxes(fixedrange = True, row = 1, col = 1)
fig.update_yaxes(fixedrange = True, row = 2, col = 1)
fig.show()
The interactive line graph below shows the relative strength index (RSI) for the stock. RSI values can indicate whether or not a stock is due for a correction:
In a broader sense:
fig = make_subplots(rows = 2, cols = 1, shared_xaxes = True)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Close'], name = 'Closing Price'), row = 1, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['RSI'], name = 'Relative Strength Index', line = dict(color = 'purple')), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([50]).repeat(len(stock)), showlegend = False, hoverinfo = 'skip', line = dict(color = 'black', dash = 'dot')), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([30]).repeat(len(stock)), fill = 'tozeroy', showlegend = False, hoverinfo = 'skip', line = dict(color = 'green', dash = 'dash')), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([70]).repeat(len(stock)), showlegend = False, hoverinfo = 'skip', line = dict(color = 'red', dash = 'dash')), row = 2, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([100]).repeat(len(stock)), fill = 'tonexty', showlegend = False, hoverinfo = 'skip', line = dict(color = 'red')), row = 2, col = 1)
fig.update_layout(title_text = 'Relative Strength Index (RSI)')
fig.update_xaxes(title_text = 'Date', row = 2, col = 1)
fig.update_yaxes(title_text = 'Price', row = 1, col = 1)
fig.update_yaxes(title_text = 'Relative Strength Index', row = 2, col = 1)
fig.update_yaxes(nticks = 10, row = 1, col = 1)
fig.update_yaxes(nticks = 10, row = 2, col = 1)
fig.update_yaxes(fixedrange = True, row = 1, col = 1)
fig.update_yaxes(fixedrange = True, row = 2, col = 1)
fig.show()
The interactive line graph below shows the on-balance volume (OBV) for the stock. The OBV typically confirms trends:
Thus, the stock price tends to follow the direction of the on-balance volume graph.
fig = make_subplots(rows = 2, cols = 1, shared_xaxes = True)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Close'], name = 'Closing Price'), row = 1, col = 1)
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['OBV'], name = 'On-Balance Volume'), row = 2, col = 1)
fig.update_layout(title_text = 'On-Balance Volume (OBV)')
fig.update_xaxes(title_text = 'Date', row = 2, col = 1)
fig.update_yaxes(title_text = 'Price', row = 1, col = 1)
fig.update_yaxes(title_text = 'On-Balance Volume', row = 2, col = 1)
fig.update_yaxes(nticks = 10, row = 1, col = 1)
fig.update_yaxes(nticks = 10, row = 2, col = 1)
fig.update_yaxes(fixedrange = True, row = 1, col = 1)
fig.update_yaxes(fixedrange = True, row = 2, col = 1)
fig.show()
The code below develops an investment strategy based on the stock market indicators listed above:
According to their values, each indicator is assigned a score ranging from -1 to 1. The total score is calculated and if it is greater than or equal to 2, the stock is given a buy rating. If the total is less than or equal to -2, the stock is given a sell rating. Otherwise, the stock is given a hold rading.
In addition, the code uses these ratings to simulate the purchase and sale of stock shares given an initial $1 million investment. The strategy is compared to a buy and hold strategy, in which the investor does not make any trades following an initial investment.
stock['Cash'] = pd.Series([0]).repeat(len(stock)).values
stock['Cash'][0] = 1000000
stock['Shares'] = pd.Series([0]).repeat(len(stock)).values
stock['Custom Strategy'] = pd.Series([1000000]).repeat(len(stock)).values
shares_buy_and_hold = stock['Cash'][0] // stock['Close'][0]
cash_buy_and_hold = stock['Cash'][0] - (shares_buy_and_hold * stock['Close'][0])
stock['Buy and Hold Strategy'] = pd.Series([1000000]).repeat(len(stock)).values
def strategy(i):
if i > 0:
stock['Cash'][i] = stock['Cash'][i - 1]
stock['Shares'][i] = stock['Shares'][i - 1]
stock['Custom Strategy'][i] = stock['Cash'][i] + stock['Shares'][i] * stock['Close'][i]
stock['Buy and Hold Strategy'][i] = cash_buy_and_hold + shares_buy_and_hold * stock['Close'][i]
info = stock.loc[i].squeeze()
date = info['Date']
indicators = {'Date' : date, 'SMA' : 0.0, 'MACD' : 0.0, 'RSI' : 0.0, 'OBV' : 0.0, 'Total' : 0.0, 'Rating' : 'Hold'}
if info['SMA50'] > info['SMA200']:
indicators['SMA'] += 1.0
elif info['SMA50'] < info['SMA200']:
indicators['SMA'] -= 1.0
if info['MACD'] > 0:
indicators['MACD'] += 2/3
elif info['MACD'] < 0:
indicators['MACD'] -= 2/3
if info['MACD'] > info['MACDSignal']:
indicators['MACD'] += 1/3
elif info['MACD'] < info['MACDSignal']:
indicators['MACD'] -= 1/3
if info['MACDHist'] > 0 and info['MACDHist'] < info['MACDHist_SMA']:
indicators['MACD'] -= 1/3
elif info['MACDHist'] < 0 and info['MACDHist'] >info['MACDHist_SMA']:
indicators['MACD'] += 1/3
if info['RSI'] < 30:
indicators['RSI'] += 1.0
elif info['RSI'] > 70:
indicators['RSI'] -= 1.0
elif info['RSI_SMA'] < 50 and info['RSI'] > 50:
indicators['RSI'] += 2/3
elif info['RSI_SMA'] > 50 and info['RSI'] < 50:
indicators['RSI'] -= 2/3
if info['OBV'] > info['OBV_SMA']:
indicators['OBV'] += 1.0
elif info['OBV'] < info['OBV_SMA']:
indicators['OBV'] -= 1.0
indicators['Total'] = indicators['SMA'] + indicators['MACD'] + indicators['RSI'] + indicators['OBV']
if indicators['Total'] >= 2.0:
indicators['Rating'] = 'Buy'
elif indicators['Total'] <= -2.0:
indicators['Rating'] = 'Sell'
if indicators['Rating'] == 'Buy':
shares = info['Cash'] // info['Close']
cash = shares * info['Close']
if stock['Cash'][i] - cash >= 0:
stock['Cash'][i] -= cash
stock['Shares'][i] += shares
elif indicators['Rating'] == 'Sell':
shares = info['Shares']
cash = shares * info['Close']
if stock['Shares'][i] - shares >= 0:
stock['Cash'][i] += cash
stock['Shares'][i] -= shares
return indicators
The code below uses the strategy documented above to indicate whether an investor should buy, sell, or hold the stock.
print(strategy(len(stock) - 1)['Rating'])
The code below implements the simulation described above using stock market data from the past 5 years to see the results of the strategy given a $1 million investment.
for i in range(len(stock)):
strategy(i)
stock
The code below creates an interactive graph comparing your portfolio value when using:
fig = go.Figure()
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Custom Strategy'], name = 'Custom Strategy'))
fig.add_trace(go.Scatter(x = stock['Date'], y = stock['Buy and Hold Strategy'], name = 'Buy and Hold Strategy'))
fig.add_trace(go.Scatter(x = stock['Date'], y = pd.Series([1000000]).repeat(len(stock)), showlegend = False, hoverinfo = 'skip', line = dict(color = 'black', dash = 'dot')))
fig.update_layout(title_text = 'Portfolio Value',
xaxis_title = 'Date',
yaxis_title = 'Portfolio Value',
xaxis_rangeslider_visible = True,
showlegend = True)
fig.update_yaxes(nticks = 10)
fig.show()